Repository navigation
Conversation
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f38b1f88fe
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Builds on the registry directions (#12713) and id-keyed setters (#12715, included here): KnownTagCodec resolves names by direction, TagMap finds a shared name's tag whichever direction it holds, and DDSpanContext resolves direction-dependent names by the span's kind. Squashed from the review history of #12731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c3c341e to
e49a183
Compare
Adds id-keyed setters, getters and removal to TagMap, TagMap.Entry and spans, so a writer that knows the tag skips the name lookup. Squashed from the review history of #12715. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c947ea5 to
55ab351
Compare
e49a183 to
44e132c
Compare
There was a problem hiding this comment.
Adds id-keyed setters, getters and removal to TagMap, TagMap.Entry and spans, so a writer that knows the tag skips the name lookup. Squashed from the review history of #12715. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds on the registry directions (#12713) and id-keyed setters (#12715, included here): KnownTagCodec resolves names by direction, TagMap finds a shared name's tag whichever direction it holds, and DDSpanContext resolves direction-dependent names by the span's kind. Squashed from the review history of #12731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55ab351 to
b569cd2
Compare
Adds id-keyed setters, getters and removal to TagMap, TagMap.Entry and spans, so a writer that knows the tag skips the name lookup. Squashed from the review history of #12715. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds on the registry directions (#12713) and id-keyed setters (#12715, included here): KnownTagCodec resolves names by direction, TagMap finds a shared name's tag whichever direction it holds, and DDSpanContext resolves direction-dependent names by the span's kind. Squashed from the review history of #12731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b569cd2 to
3ab5862
Compare
span.setTag(0L, "") or a null value reached TagMap.getAndRemove(long), which rejects unknown ids, so clearing threw IllegalArgumentException while every id-keyed setter ignores an unknown id. removeTag(long) now ignores it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The long-valued setters prechecked with needsIntercept, but when the interceptor declined the tag they discarded the box it was given and stored the primitive. Follow the same precheckIntercept -> setBox shape as the other primitive setters, so TagMap keeps the box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
peer.port is declared per direction in the registry (#12713), so it has no direction-free id: BaseDecorator, shared by client and server decorators, sets it by name, and the set-by-id tests use an unshared int tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Builds on the registry directions (#12713) and id-keyed setters (#12715, included here): KnownTagCodec resolves names by direction, TagMap finds a shared name's tag whichever direction it holds, and DDSpanContext resolves direction-dependent names by the span's kind. Squashed from the review history of #12731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ID-keyed peer.port entries lose their identity in read-through TagMaps: removing one direction hides the other, and replacing an inherited entry reports no previous value.
🤖 Bits Code Review · Commit de00517
| public Entry getAndRemove(long tagId) { | ||
| this.checkWriteAccess(); | ||
|
|
||
| return this.getAndRemove(Entry.requireKnownName(tagId), tagId); |
There was a problem hiding this comment.
Preserve tag identity in read-through tombstones
When a frozen parent contains both PEER_PORT_INBOUND_ID and PEER_PORT_OUTBOUND_ID, removing the inbound ID from its child also hides the outbound entry. Tombstones store the shared name "peer.port", so subsequent lookups suppress both IDs instead of only the removed one. Track tag identity consistently across tombstone creation, lookup, clearing, and inherited iteration.
React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it
What each reaction means
- 👍 Useful: correct, and you're fixing it or will.
- 👎 Wrong: the finding is incorrect, for example the bug can't happen or the code was misread.
- 😕 Unclear: you can't tell what it means or why it matters.
- 👀 Right, not acting: correct, but intended, out of scope for this PR, a known risk, or too minor.
🤖 Bits Code Review · Open Bits AI session
| // an OpenTelemetry rename must canonicalize the same way, or it would hash to the wrong bucket | ||
| // and silently miss the entry stored under the Datadog name. | ||
| String canonicalTag = KnownTagCodec.canonicalTagName(tag); | ||
| long tagHash = Entry.tagHashOf(tag); |
There was a problem hiding this comment.
Preserve tag identity when retrieving inherited previous values
When a frozen parent stores PEER_PORT_OUTBOUND_ID, child.getAndSet(Entry.create(PEER_PORT_OUTBOUND_ID, newValue)) returns null instead of the inherited previous entry. Its parent fallback calls getEntry(newEntry.tag), which hashes the shared name "peer.port" as a custom tag and misses the ID-keyed entry. Preserve newEntry.tagHash in the parent fallback to maintain the previous-value contract. That fallback lies outside the diff hunks.
React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it
What each reaction means
- 👍 Useful: correct, and you're fixing it or will.
- 👎 Wrong: the finding is incorrect, for example the bug can't happen or the code was misread.
- 😕 Unclear: you can't tell what it means or why it matters.
- 👀 Right, not acting: correct, but intended, out of scope for this PR, a known risk, or too minor.
🤖 Bits Code Review · Open Bits AI session
What Does This Do
Adds setting a known tag by its id to
TagMapand the span API, on top of the tag registry from #12354 (stacked on it; the diff here is only this PR).TagMap.set(long tagId, value)forObject,CharSequence, and the primitives. The entry is built under the tag's canonical name fromKnownTagCodec.nameOf, skipping the canonicalizing name lookup theStringsetters pay on every new entry. An id that names no known tag is rejected.TagMap.Entry.create(long tagId, value), the public way to build an entry ahead of time (as decorators do for cached entries), at the same layer asAgentSpan.setTag. It has the same contract as the name-keyedcreate: a null or empty value yields no entry.AgentSpan.setTag(long tagId, value), as default methods that resolve the id to its name and delegate to theStringsetter, so everyAgentSpanimplementation stays correct with no changes.DDSpan/DDSpanContextoverrides with the same behavior as theStringfamily:TagInterceptorstill runs on the tag's name (it is name-keyed), primitives still box only when the tag might be intercepted, and thehttp.status_codequirk is kept. The difference is that a stored tag is set by id.TagMaphashes entries by tag id.TagMap.Entryholds a 64-bit tag hash, computed once at construction: a known tag's id, or a custom tag's name hash in the low 32 bits. An id's serial sits in bits 63-48, so the two can never collide.tagId()becomes a field read instead of akeyOflookup.getEntry,getAndRemove) take onekeyOf, instead of canonicalizing and then hashing the name separately.BucketGroupkeeps itsinthashes, so onlyEntrygrows:inttolongcosts 8 bytes per entry because of alignment. That is an accepted temporary cost until the dense store.BaseDecorator.onPeerConnection/setPeerPort, which run on every client span, now setpeer.hostname,peer.ipv4/ipv6andpeer.portbyKnownTagsid.Motivation
Under the OTLP <-> Datadog mapping, an ID identifies one tag exactly, where a name may not. #12713 is making the tag registry direction-aware, and there
peer.portbecomes two tags (the server's port on outbound spans, the client's on inbound ones) that share a Datadog name. Writers then need to set the specific tag, which is what an id expresses. This PR provides that API, independent of the direction work:It follows the API shape of #11901, but implemented on the current
TagMaprather than the dense store, which #11901 is stacked on. The dense store can later swap the implementation behind the same signatures.Additional Notes
Ledger/builder support, which will arrive with a caller. Id-keyedTagMap.getEntry(long)/getAndRemove(long)are included, so clearing a tag by id removes it by id rather than round-tripping through its name.Mapcontract unchanged, deliberately:Entry.hashCode()still returns the bucket hash rather thankey.hashCode() ^ value.hashCode(), andTagMapstill compares by identity. Both were already the case before this PR; changing them is a separate behavior change.PEER_PORT_OUTBOUND_ID) hashes by that id, so on this PR alone a lookup by the shared name"peer.port"will not find it. Phase 3 (Resolve direction-dependent tag names by the span's kind (otlp - tag registry - phase 3) #12731) handles both sides: spans resolve the name by their direction, and aTagMaplookup by the shared name probes its per-direction ids.peer.port,KnownTags.PEER_PORT_IDno longer exists, soBaseDecorator.setPeerPortstops compiling until it picks a direction. That is intended: it forces the direction to be chosen explicitly.PEER_PORT_ID.setPeerPort:peer.hostname/ipv4/ipv6keep the same ids in Scope OpenTelemetry tag names by span direction in the tag registry (otlp - tag registry - phase 2) #12713, soonPeerConnectionstays a real caller with no coupling.peer.port, which server decorators also set for the client's port, moves to the direction-aware follow-up.peer.portinto a tag per direction, a tag stored under a per-direction id hashes by that id, but the shared name"peer.port"resolves to no id, so a lookup by name misses it. IfsetPeerPortswitched toPEER_PORT_OUTBOUND_ID, a latergetTag("peer.port")would come back empty. Until direction-aware reads land,setPeerPorttherefore has to keep settingpeer.portby name, which stores it as a custom tag that name lookups still find. So whichever of the two PRs merges second keepssetPeerPortname-keyed;onPeerConnection'speer.hostname/ipv4/ipv6keep the same ids in Scope OpenTelemetry tag names by span direction in the tag registry (otlp - tag registry - phase 2) #12713 and are unaffected.AgentSpanwould see the id-keyed call directly, since a mock intercepts the default method, so every call site moved to ids would break its test's expectations. Instead, the decorator specs now mockNameKeyedAgentSpan, a test-onlyAgentSpanwhose id-keyed overloads arefinaland delegate to the name-keyed ones. A mock can't override a final method, so it only seessetTag(name, value).BaseDecoratorTest.mockSpan()creates one. The specs' expectations are unchanged from master, and moving further call sites to ids needs no test change. (Spock only allowsMock()inside a spec class, so the factory lives on the base spec rather than in a trait. Other modules' 50-oddMock(AgentSpan)sites can adopt it as they migrate.)Tags.PEER_PORTand friends stay as they are; a@Deprecatedpointing at the ids comes once callers have migrated.Contributor Checklist
:internal-api:test(TagMap*incl.TagMapFuzzTest, 384 tests;KnownTags*; newTagMapSetByIdTest,TagMapTagHashTest,TagMapEntryCreateByIdTest),:internal-api:spotlessCheck:dd-trace-core:test(DDSpan*incl. newDDSpanSetTagByIdTest,*taginterceptor*,*otlp*,common.writer.*,common.metrics.*; 1279 tests),:dd-trace-core:spotlessCheck.DDAgentWriterCombinedTest.unixSocketTimeoutKeepsWorkerAliveAndReconnectsfailed once and passed on rerun here and on the base branch; it's a timing test.New
TagMapInsertBenchmark, before vs. after hashing entries by tag id. MacBook, JDK 21, default flags,@Fork(2)triage; 12 tags per op:knownByNameknownByIdcustomByNameThe first run caught a regression:
knownByIdwas +17%, because the id constructor resolved the name twice. That is fixed in 62f1026. Known tags net only +48 B because spreading them by serial removed a bucket collision.:dd-java-agent:agent-bootstrap:test(*Decorator*, 413 tests),:dd-java-agent:agent-bootstrap:spotlessCheckJira ticket
N/A
🤖 Generated with Claude Code